/*
 *  1 800 709 7779 for quesion
 * Documetation
 * http://apps.cybersource.com/library/documentation/dev_guides/Decision_Manager_DG/html/
 *
 * compuvest.p12 need store to $GLASSFISH_HOME/glassfish/domains/domain1/config
 */
package com.compuvest.ics;

// <editor-fold defaultstate="collapsed" desc="import">
import com.compuvest.web.Basket;
import com.compuvest.web.Customer;

import com.cybersource.ws.client.Client;
import com.cybersource.ws.client.ClientException;
import com.cybersource.ws.client.FaultException;

import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
// </editor-fold>

public class ICSAuth implements Serializable {

    public static final String PATH = System.getProperty("user.dir");
    // for glassfish 2.2.1
    public static final String ICS_PATH = PATH + "/../applications/compuvest.com/WEB-INF/ics/";
    // for glassfish 3.1
    //public static final String ICS_PATH = PATH + "/../applications/j2ee-modules/compuvest.com/WEB-INF/ics/";
    public static final String ICS_LOG  = PATH + "/../logs/";

    public static final String URL_LIVE  = "https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/";
    public static final String URL_TEST  = "https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/";

    private final DecimalFormat df;
    private final Properties props;

// <editor-fold defaultstate="collapsed" desc="ICSAuth()">

    public ICSAuth() {

        props = new Properties();
        props.put("merchantID", "compuvest");
        props.put("targetAPIVersion", "1.28");
        props.put("sendToProduction", "false");
//        props.put("serverURL", URL_TEST); // test
        props.put("serverURL", URL_LIVE); // live
        props.put("enableLog", "true");
        props.put("keysDirectory", ICS_PATH);
        props.put("logDirectory",  ICS_LOG);
        props.put("logMaximumSize", "10");

        df = new DecimalFormat("0.00");
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="getICSStat()">

    public String getICSStat(Basket basket) {

        Map<String,String> reply = runAuth(this.props, basket);
        if(reply != null) {
            try {
                // if auth was successful, run capture
//            return runCapture(this.props, requestID);
                return processReply(reply);
            }
            catch(ClientException e) {
                System.out.println("ics.ICSStat():\n" + e.getMessage());
            }
        }
        return "";
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="runAuth()">

/*
 * card_accountNumber   Number given account.           ccAuthService,ccCreditService
 * card_expirationMonth Two-digit expires in.           ccAuthService,ccCreditService
 * card_expirationYear Four-digit card expires.         ccAuthService,ccCreditService
 *
 */
    public Map<String,String> runAuth(Properties prop, Basket basket) {

        Customer cust = basket.getCustomer();
        Map<String,String> request = new HashMap<>();
        switch (basket.getPaymentMethod()) {
            case "CC":
                request.put("ccAuthService_run", "true");
                request.put("ccCaptureService_run", "true");
                request.put("card_accountNumber", "" + cust.getCardNumber());
                request.put("card_expirationMonth", cust.getCExpMonth());
                request.put("card_expirationYear", cust.getCExpYears());
                break;
            case "CD":
                request.put("ecDebitService_run", "true");
                request.put("check_accountType", "c");
                request.put("billTo_driversLicenseNumber", cust.getLicenseNumber());
                request.put("billTo_driversLicenseState", cust.getLicenseState());
                request.put("check_checkNumber", cust.getCheckNumber());
                request.put("check_accountNumber", cust.getCheckAccountNumber());
                request.put("check_bankTransitNumber", cust.getCheckRoutingNumber());
                break;
            default:
                return null;
        }

        request.put("merchantReferenceCode", basket.getOrderNumber());
        request.put("merchantID", "compuvest");

        request.put("afsService_run", "true");
        request.put("ignore_bad_cv", "true");

        request.put("billTo_ipAddress", basket.getCompuvestCookies().getIPAddress());

    	request.put("billTo_email", cust.getEmail());
    	request.put("billTo_phoneNumber", cust.getBilling().getPhone());
        request.put("billTo_firstName", cust.getBilling().getFirstName());
    	request.put("billTo_lastName", cust.getBilling().getLastName());
    	request.put("billTo_street1", cust.getBilling().getAddress1());
    	request.put("billTo_street2", cust.getBilling().getAddress2());
        request.put("billTo_city", cust.getBilling().getCity());
    	request.put("billTo_state", cust.getBilling().getState());
    	request.put("billTo_postalCode", cust.getBilling().getZip());
    	request.put("billTo_country", cust.getBilling().getCountry());

        request.put("shipTo_firstName", cust.getShipping().getFirstName());
    	request.put("shipTo_lastName", cust.getShipping().getLastName());
    	request.put("shipTo_street1", cust.getShipping().getAddress1());
    	request.put("shipTo_street2", cust.getShipping().getAddress2());
    	request.put("shipTo_city", cust.getShipping().getCity());
    	request.put("shipTo_state", cust.getShipping().getState());
    	request.put("shipTo_postalCode", cust.getShipping().getZip());
    	request.put("shipTo_country", cust.getShipping().getCountry());

        request.put("item_0_productSKU", basket.getOrderNumber());
    	request.put("item_0_productName", basket.getOrderNumber());
    	request.put("item_0_quantity", "" + basket.getCountItems());
    	request.put("item_0_unitPrice", df.format(basket.getGrandTotal()));
    	request.put("purchaseTotals_grandTotalAmount", df.format(basket.getGrandTotal()));
       	request.put("purchaseTotals_currency", "USD");

        try {
//            if(("CD".equals(basket.getPaymentMethod()))) {
//                displayMap("CREDIT CARD AUTHORIZATION REQUEST:", request);
//            }
            // run transaction now
            Map reply = Client.runTransaction(request, prop);
//            if(("CD".equals(basket.getPaymentMethod()))) {
//                displayMap("CREDIT CARD AUTHORIZATION REPLY:", reply);
//            }

            return reply;
        }
        catch(ClientException e) {
            System.out.println("ics.ICSAuth:runAuth():\n" + e.getMessage());
            if(e.isCritical()) {
                handleCriticalException(e, request);
            }
        }
        catch(FaultException e)	{
            System.out.println("ics.ICSAuth:runAuth():\n" + e.getMessage());
            if(e.isCritical())	{
                handleCriticalException(e, request);
            }
        }

        return null;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="processReply()">

    private String processReply(Map<String,String> reply) throws ClientException {

        MessageFormat template = new MessageFormat(getTemplate((String)reply.get("decision")));

        Object[] content = { getContent(reply) };

        return template.format(content);
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="getTemplate()">

    private String getTemplate(String decision) {

        if("ACCEPT".equalsIgnoreCase(decision))
            return "Order was approved.{0}";

        if("REJECT".equalsIgnoreCase(decision))
            return "Order was not approved.{0}";

        if("ERROR".equalsIgnoreCase(decision))
            return "ICS server error.{0}";

        // ERROR
        return "Order cannot be completed at this time.{0}\nPlease try again later.";
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="getContent()">

    private String getContent(Map<String,String> reply) throws ClientException {

        String avsStr = "";
        String avsCode = reply.get("ccAuthReply_avsCode");

        if(avsCode != null && avsCode.length() > 0) {
            switch(avsCode.charAt(0)) {
                case 'A': avsStr = "Payment processor timeout.May or may not have went through."; break;
                case 'B': avsStr = "Street address matches.Zip code does not.International."; break;
                case 'C': avsStr = "Neither street address nor zip code match.International."; break;
                case 'D': avsStr = "Street address and zip code match. International."; break;
                case 'E': avsStr = "Billing address cannot be checked for this card type."; break;
                case 'F': avsStr = "Name does not match.Zip code matches."; break;
                case 'G': avsStr = "This international bank does not support auto-checking of billing address."; break;
                case 'H': avsStr = "Street address and zip code match.Name does not match."; break;
                case 'I': avsStr = "Neither street address nor zip code match. International."; break;
                case 'J': avsStr = "Name, street address, zip code match.Fraud protection provided by AmEx."; break;
                case 'K': avsStr = "Only name matches. Street address and zip code do not match."; break;
                case 'L': avsStr = "Name and zip code match.Street address does not match."; break;
                case 'M': avsStr = "Street address and zip code match.International."; break;
                case 'N': avsStr = "Neither street address nor zip code match."; break;
                case 'O': avsStr = "Name and street address match.Zip code does not match."; break;
                case 'P': avsStr = "Zip code matches.Street address does not match.International."; break;
                case 'Q': avsStr = "Name,street address,zip code match."; break;
                case 'R': avsStr = "Billing address checking system unavailable."; break;
                case 'S': avsStr = "This international bank does not support auto-checking of billing address."; break;
                case 'T': avsStr = "Street address matches.Name does not match."; break;
                case 'U': avsStr = "Domestic bank doesn't support checking of billing address, or is temporarily unavailable."; break;
                case 'V': avsStr = "Name, street address, zip code match."; break;
                case 'W': avsStr = "Zip code +4 matches.Street address does not match."; break;
                case 'X': avsStr = "Street address and zip code +4 match."; break;
                case 'Y': avsStr = "Street address and zip code match."; break;
                case 'Z': avsStr = "Zip code matches.Street address does not match."; break;
                default:  avsStr = "Not defined ccAuthReply_avsCode.";
            }
        }
        String authCode = reply.get("ccAuthReply_authorizationCode");
        if(authCode != null) {
            avsStr += " [" + authCode + "]";
        }

        int reasonCode = 0;
        try {
            reasonCode = Integer.parseInt(reply.get("reasonCode"));
        }
        catch(Exception ignore) { }

        String reasonResult = "(" + reasonCode;
        switch(reasonCode) {
            case 100: reasonResult = ") Transaction succeeds: "; break;
            case 101: reasonResult = ") The required field(s) are missing: " + enumerateValues(reply, "missingField"); break;
            case 102: reasonResult = ") A field is garbled. Try again with corrected information: " + enumerateValues(reply, "invalidField"); break;
            case 150: reasonResult = ") General system failure. May or may not have went through: "; break;
            case 151:
            case 152: reasonResult = ") Cybersource was received but there was a server time-out: "; break;
            case 200: reasonResult = ") Approved by bank but declined by Cybersource: "; break;
            case 201: reasonResult = ") Issuing bank has questions. Call them for verbal authorization: "; break;
            case 202: reasonResult = ") Expired card, or mismatched expiration date: "; break;
            case 203: reasonResult = ") General decline of card. No other information: "; break;
            case 204: reasonResult = ") Insufficient funds: "; break;
            case 205: reasonResult = ") Stolen or lost card: "; break;
            case 207: reasonResult = ") Issuing bank unavailable. Wait a few minutes, then try again: "; break;
            case 208: reasonResult = ") Inactive card, or card disallows card-not-present transactions: "; break;
            case 209: reasonResult = ") Three digit CID code does not match. AmEx: "; break;
            case 210: reasonResult = ") Card has reached its credit limit: "; break;
            case 211: reasonResult = ") Invalid card verification number: "; break;
            case 220: reasonResult = ") The processor declined the request based on a general issue with the customer's account: "; break;
            case 221: reasonResult = ") Customer (not card) black-flagged by processor: "; break;
            case 222: reasonResult = ") The customer's bank account is frozen: "; break;
            case 230: reasonResult = ") Approved by bank but declined by Cybersource: back-of-card digits check failed: "; break;
            case 231: reasonResult = ") Invalid account number: "; break;
            case 232: reasonResult = ") Card type not accepted by payment processor: "; break;
            case 233: reasonResult = ") General decline of card by payment processor: "; break;
            case 234: reasonResult = ") Merchant's CyberSource account configured improperly: "; break;
            case 235: reasonResult = ") Requested amount exceeds authorized amount: "; break;
            case 236: reasonResult = ") Processor failure. Wait a few minutes, then try again: "; break;
            case 237:
            case 238: reasonResult = ") Authorization has already been reversed: "; break;
            case 239: reasonResult = ") Requested amount must match previous transaction amount: "; break;
            case 240: reasonResult = ") Card type mismatched with account number: "; break;
            case 241: reasonResult = ") Request ID invalid.Try again: "; break;
            case 242: reasonResult = ") No authorization to capture: "; break;
            case 243: reasonResult = ") Transaction already settled/reversed: "; break;
            case 246: reasonResult = ") Transaction cannot be voided: "; break;
            case 247: reasonResult = ") Transaction was previously voided: "; break;
            case 250: reasonResult = ") Payment processor timeout.May or may not have went through: "; break;
            case 388: reasonResult = ") The routing number did not pass verification: "; break;
            case 400: reasonResult = ") The fraud score exceeds your threshold: "; break;
            case 480: reasonResult = ") The order is marked for review by Decision Manager: "; break;
            case 481: reasonResult = ") The order is rejected by Decision Manager: "; break;
//            case 480: reasonResult = ": "; break;
            default:  reasonResult = ") Not defined reasonCode: ";
        }

        return reasonResult + avsStr;
}
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="enumerateValues()">

    private String enumerateValues(Map reply, String fieldName) {

        StringBuilder sb = new StringBuilder();
        String key, val;
        for(int i = 0; ; ++i) {
            key = fieldName + '_' + i;
            if(!reply.containsKey(key)) break;

            val = (String)reply.get(key);
            if(val != null) {
                sb.append(val).append('\n');
            }
        }
        return sb.toString();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="runCapture()">
//-----------------------------------------------
//
/*
    public HashMap runCapture(Properties prop, String authRequestID) {

   	HashMap<String,String> request = new HashMap<String,String>();
        HashMap reply;

        request.put("ccCaptureService_run", "true");
	request.put("merchantReferenceCode", "compuvest");
	// reference the requestID returned by the previous auth.
	request.put("ccCaptureService_authRequestID", authRequestID);

        System.out.println("---------authRequestID="+authRequestID);
	try {
            displayMap("FOLLOW-ON CAPTURE REQUEST:", request);

            // run transaction now
            reply = Client.runTransaction(request, prop);

            processReply(reply);

            displayMap("FOLLOW-ON CAPTURE REPLY:", reply);
	}
	catch(ClientException e) {
            System.out.println("ICSAuth:runCapture:\n" + e.getMessage());
            if(e.isCritical()) {
		handleCriticalException(e, request);
            }
	}
	catch(FaultException e)	{
            System.out.println("ICSAuth:runCapture:\n" + e.getMessage());
            if(e.isCritical()) {
		handleCriticalException(e, request);
            }
	}
        return reply;
    }
*/

// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="displayMap()">
/*
    private void displayMap(String header, Map<String,String> map) {

        StringBuilder dest = new StringBuilder();
        dest.append(header).append('\n');

        if(map != null && !map.isEmpty()) {
            Iterator iter = map.keySet().iterator();
            String key;
            while(iter.hasNext()) {
                key = (String)iter.next();
                dest.append(key).append('=').append((String)map.get(key)).append('\n');
            }
        }
        System.out.println(dest.toString());
    }
*/
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="handleCriticalException()">

    private void handleCriticalException(ClientException e, Map request) {
        // send the exception and order information to the appropriate
        // personnel at your company using any suitable method, e.g. e-mail,
        // multicast log, etc.
    }

    private void handleCriticalException(FaultException e, Map request) {
        // send the exception and order information to the appropriate
        // personnel at your company using any suitable method, e.g. e-mail,
        // multicast log, etc.
    }
// </editor-fold>

}
